package com.fsh.lingsp.common.common.utils.discover;

import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.fsh.lingsp.common.common.utils.FutureUtils;
import com.fsh.lingsp.common.common.utils.discover.domain.UrlInfo;
import lombok.extern.slf4j.Slf4j;
//import org.jetbrains.annotations.Nullable;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 作者：fsh
 * 日期：2024/03/15
 * <p>
 * 描述：定义一个抽象类AbstractUrlDiscover，它实现了UrlDiscover接口
 */
@Slf4j
public abstract class AbstractUrlDiscover implements UrlDiscover {
    // 定义一个私有的静态正则表达式常量PATTERN，用于匹配URL
    // 这个正则表达式能够匹配http或https开头的URL，以及可能存在的www前缀和域名及路径
    private static final Pattern PATTERN =
            Pattern.compile("((http|https)://)?(www.)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?");

    /**
     * 实现UrlDiscover接口中的getUrlContentMap方法，用于从内容中提取URL并返回映射
     *
     * @param content 传递的一串包含 url链接 的长文本
     */
    @Nullable
    @Override
    public Map<String, UrlInfo> getUrlContentMap(String content) {
        // 如果内容为空或只包含空白字符，则返回空映射
        if (StrUtil.isBlank(content)) {
            return new HashMap<>();
        }
        // 1、使用正则表达式从内容中查找所有匹配的URL。  ReUtil是hutool的工具类。
        List<String> matchList = ReUtil.findAll(PATTERN, content, 0);

        // 对每个匹配的URL创建CompletableFuture对象，并行处理以获取UrlInfo
        List<CompletableFuture<Pair<String, UrlInfo>>> futures = matchList.stream().map(match -> CompletableFuture.supplyAsync(() -> {
            // 调用getContent方法获取UrlInfo
            UrlInfo urlInfo = getContent(match);
            // 如果获取的UrlInfo为null，则返回null，否则返回匹配URL和UrlInfo的Pair对象
            return Objects.isNull(urlInfo) ? null : Pair.of(match, urlInfo);
        })).collect(Collectors.toList());

        // 等待所有CompletableFuture完成，并返回非空的Pair列表
        CompletableFuture<List<Pair<String, UrlInfo>>> future = FutureUtils.sequenceNonNull(futures);

        // 将结果列表转换为Map，其中键是URL，值是UrlInfo
        // 如果多个URL对应相同的UrlInfo，则保留第一个
        return future.join().stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond, (a, b) -> a));
    }


    /**
     * 用于从URL获取内容并返回UrlInfo对象
     *
     * @param url 网址
     * @return {@link UrlInfo }
     */
    @Nullable
    @Override
    public UrlInfo getContent(String url) {
        // 调用assemble方法组装完整的URL（如果未以http开头，则添加http://）
        Document document = getUrlDocument(assemble(url));
        // 如果获取的文档为null，则返回null
        if (Objects.isNull(document)) {
            return null;
        }

        // 构建UrlInfo对象，并设置标题、描述和图像
        // 这里的 getTitle()、getDescription()、getImage()方法均为子类扩展实现的。
        // 因为不同的网站 标签还不太一样。（比如本项目就分了两个 ： 普通平常的网站 ， 微信文章端的网站）
        return UrlInfo.builder()
                .title(getTitle(document))
                .description(getDescription(document))
                .image(getImage(assemble(url), document)).build();
    }

    // 私有方法，用于组装URL，如果URL未以http开头，则添加http://
    private String assemble(String url) {
        // 使用StrUtil的startWith方法检查URL是否以http开头
        if (!StrUtil.startWith(url, "http")) {
        // 如果不是，则返回添加http://前缀的URL
        return "http://" + url;
    }
    // 如果已经是完整的URL，则直接返回
        return url;
}

    /**
     * 受保护的方法，用于获取URL对应的文档对象。
     *
     * 针对github这种网站，请求时间可能会很长，它决定了用户的等待上限。
     * 我们要对它们做一个熔断，也就是超过2s，没拿到网站，就算了
     *
     * @param matchUrl 匹配网址
     * @return {@link Document }
     */
    protected Document getUrlDocument(String matchUrl) {
        try {
            // 使用Jsoup库创建Connection对象，用于连接URL
            Connection connect = Jsoup.connect(matchUrl);
            // 设置连接超时时间为2000毫秒
            connect.timeout(2000);
            // 执行GET请求并返回文档对象
            return connect.get();
        } catch (Exception e) {
            // 如果发生异常，则记录错误信息并返回null
            log.error("find error:url:{}", matchUrl, e);
        }
        // 如果没有异常，但连接失败或文档为空，也返回null
        return null;
    }

    /**
     * 判断链接是否有效
     * 输入链接
     * 返回true或者false
     */
// 定义一个公共的静态方法，接收一个字符串类型的URL地址作为参数，返回一个布尔值表示链接是否有效。
    public static boolean isConnect(String href) {
        // 定义URL对象，用于存储解析后的URL地址。
        URL url;
        // 定义整型变量，用于存储HTTP请求的状态码。
        int state;
        // 定义字符串变量，用于存储下载链接的类型（如果有的话）。
        String fileType;

        try {
            // 根据传入的字符串参数`href`创建一个URL对象。
            url = new URL(href);

            // 打开到URL的连接，并强制转换为HttpURLConnection类型。
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();

            // 获取HTTP请求的状态码，并存储到变量`state`中。
            state = httpURLConnection.getResponseCode();

            // 获取HTTP响应头中的"Content-Disposition"字段，通常用于表示文件下载的类型，并存储到变量`fileType`中。
            fileType = httpURLConnection.getHeaderField("Content-Disposition");

            // 如果状态码是200（成功）、302（临时重定向）或304（未修改，使用缓存），并且`fileType`为空（表示不是下载链接），
            // 则认为这是一个有效的链接，返回true。
            if ((state == 200 || state == 302 || state == 304) && fileType == null) {
                return true;
            }

            // 断开与URL的连接。
            httpURLConnection.disconnect();
        } catch (Exception e) {
            // 如果在尝试连接URL或处理连接时发生任何异常，则捕获该异常，并返回false表示链接无效。
            return false;
        }

        // 如果上述所有条件都不满足，则返回false表示链接无效。
        return false;
    }
}
